package jmars.mobile.worker;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import jmars.mobile.common.Investigator;
import jmars.mobile.common.PropertiesLoader;
import jmars.mobile.common.Sha;
import jmars.mobile.datainvestigation.Summary;

/**
 * 1st job: compute certainty for every interval and the total certainty
 * 2nd job: compute variance and mean for each interval
 * both of those two jobs are divided into work day and weekend
 * 
 * group=1,2
 * intervalMinutes=10,20,30,60...
 * @author Jan
 *
 */
public class TransitionBTSMeasure implements Investigator {
	
	private void addTrans(Map<Long,Map<String,Map<String,Float>>> trans,Long time,String from,String to){
		if(!trans.containsKey(time))trans.put(time, new HashMap<String,Map<String,Float>>());
		if(!trans.get(time).containsKey(from))trans.get(time).put(from, new HashMap<String,Float>());
		if(!trans.get(time).get(from).containsKey(to))
			trans.get(time).get(from).put(to, 1F);
		else
			trans.get(time).get(from).put(to,trans.get(time).get(from).get(to)+1);
	}
	private Long computeAll(File[] files,Map<Long,Map<String,Map<String,Float>>> transProperties,Map<Long,Map<String,Float>> transStateTotal,Map<Long,Float> transTotal,Set<String> states) throws IOException{
		Long interval = Long.parseLong(PropertiesLoader.getProperty("time.intervalMinutes"))*60000;
		Long buffer = 10*60*1000L;
		Long total = 0L;
		Long line;
		int filesFinished=0;
		FileInputStream fis;
		BufferedReader reader;
		String row;
		String[] cols;
		Map<String,Map<Long,String>> phoneRecords=new HashMap<String,Map<Long,String>>();
		Map<Long,Map<String,Map<String,Float>>> trans=new HashMap<Long,Map<String,Map<String,Float>>>();
		
		Long beginL=null,beginR=null;
		Calendar cal=Calendar.getInstance();
		Date beginDate=null;
		Long lastTime,currentTime;
		DecimalFormat df = new DecimalFormat();
		df.applyPattern("0.000");
		String groupKey=null;
		ArrayList<Entry<Long,String>> tempList;
		Entry<Long,String> lastEntry=null,firstSameStateEntry=null;
		File inputFile;
		int l=files.length;
		Set<Entry<Long,String>> tempRemove = new HashSet<Entry<Long,String>>();
		for(int i=0;i<l;i++){
			inputFile=files[i];
			fis = new FileInputStream(inputFile);
			reader = new BufferedReader(new InputStreamReader(fis));
			line=0L;
			while((row=reader.readLine())!=null){
				line++;
				total++;
				try{
					cols=row.split(Summary.columnSeperator);
					if(Summary.filterRowExcludeLACI(cols,true,false)) continue;
					beginDate=Summary.parseBeginDate(cols);
					if(beginDate==null) continue;
					cal.setTime(beginDate);
					if(cal.get(Calendar.YEAR)!=2012) continue;
					if(beginL==null)beginL=beginDate.getTime();
					if(beginR==null)beginR=beginDate.getTime();
					if(Summary.checkLACI(cols))
						groupKey=Summary.genGroupKey(cols);
					else
						groupKey=null;
					if(!phoneRecords.containsKey(cols[0]))
						phoneRecords.put(cols[0], new HashMap<Long,String>());
					phoneRecords.get(cols[0]).put(beginDate.getTime(), groupKey);
				}catch(Exception ex){
					System.out.println("exception at line "+line+" of "+inputFile.getName());
					System.out.println(row);
					ex.printStackTrace();
					if(!Summary.errorResume){
						System.exit(0);
					}
				}
			}
			if((beginR!=null&&(beginR-beginL>interval+buffer))||i==l-1){
				//work
				for(String phone:phoneRecords.keySet()){
					tempList = new ArrayList<Entry<Long,String>>(phoneRecords.get(phone).entrySet());
					Collections.sort(tempList, new Comparator<Map.Entry<Long,String>>() {
						public int compare(Map.Entry<Long, String> o1, Map.Entry<Long, String> o2) {
							return o2.getKey()==o1.getKey()?0:(o2.getKey()<o1.getKey()?1:-1);
			            }
					});
					tempRemove.clear();
					lastEntry=null;
					firstSameStateEntry=null;
					for(Entry<Long,String> en:tempList){
						if(i<l-1&&beginR-en.getKey()<buffer) break;
						
						if(lastEntry!=null){
							lastTime=lastEntry.getKey()-lastEntry.getKey()%interval;
							currentTime=en.getKey()-en.getKey()%interval-interval;
							if((lastEntry.getValue()==null&&en.getValue()!=null)||(lastEntry.getValue()!=null&&!lastEntry.getValue().equals(en.getValue()))){
								if(en.getKey()-lastEntry.getKey()<=interval){
									this.addTrans(trans,lastTime,lastEntry.getValue(),en.getValue());
								}else{
									/*
									 * if the delta begin time is greater then interval. this process will be separated to two transition.
									 * one is from last state to null, the other one is from null to current state.
									 */
									this.addTrans(trans,lastTime,lastEntry.getValue(),null);
									this.addTrans(trans,currentTime,null,en.getValue());
								}
								firstSameStateEntry=en;
							}else{
								if(en.getKey()-firstSameStateEntry.getKey()>=interval){
									if(en.getKey()-firstSameStateEntry.getKey()<2*interval){
										this.addTrans(trans,firstSameStateEntry.getKey()-firstSameStateEntry.getKey()%interval,en.getValue(),en.getValue());
									}else{
										//treat this process as two transitions, one is from state to null, the other one is from null to state.
										this.addTrans(trans,firstSameStateEntry.getKey()-firstSameStateEntry.getKey()%interval,en.getValue(),null);
										this.addTrans(trans,currentTime,null,en.getValue());
									}
									firstSameStateEntry=en;
								}
							}
						}
						lastEntry=en;
						if(firstSameStateEntry==null)firstSameStateEntry=en;

						//prepare to remove if it's not the last state.
						if(tempList.indexOf(en)+1<tempList.size()) {
							phoneRecords.get(phone).remove(en);
							tempRemove.add(en);
						}
						else if(i==l-1){
							lastTime=en.getKey()-en.getKey()%interval;
							this.addTrans(trans,lastTime,en.getValue(),null);
						}
					}
					tempList.removeAll(tempRemove);
				}
				beginR=null;
				beginL=null;
			}
			beginR=null;
			filesFinished++;
			reader.close();
			fis.close();
			System.out.println(filesFinished+": "+inputFile.getName()+" finished at "+line);
		}
		for(Long t:trans.keySet()){
			transStateTotal.put(t, new HashMap<String,Float>());
			transTotal.put(t, 0F);
			transProperties.put(t, new HashMap<String,Map<String,Float>>());
			states.addAll(trans.get(t).keySet());
			for(String s:trans.get(t).keySet()){
				states.addAll(trans.get(t).get(s).keySet());
				transStateTotal.get(t).put(s, 0F);
				transProperties.get(t).put(s, new HashMap<String,Float>());
				for(String ts:trans.get(t).get(s).keySet()){
					transStateTotal.get(t).put(s, transStateTotal.get(t).get(s)+trans.get(t).get(s).get(ts));
				}
				transTotal.put(t, transTotal.get(t)+transStateTotal.get(t).get(s));
				for(String ts:trans.get(t).get(s).keySet()){
					transProperties.get(t).get(s).put(ts, trans.get(t).get(s).get(ts)/transStateTotal.get(t).get(s));
				}
			}
		}
		return total;
	}
	
	@Override
	public Long compute(File[] files, FileWriter writer) throws IOException {
		Long total = 0L;
		Map<Long,Map<String,Map<String,Float>>> transProperties=new HashMap<Long,Map<String,Map<String,Float>>>();
		Map<Long,Map<String,Float>> transStateTotal=new HashMap<Long,Map<String,Float>>();
		Map<Long,Float> transTotal=new HashMap<Long,Float>();
		Set<String> states=new HashSet<String>();
		Map<String,Integer> stateIndex=new HashMap<String,Integer>();
		total=computeAll(files,transProperties,transStateTotal,transTotal,states);
		writer.write("%total: "+total+"\r\n%time");
		int sit=0;
		stateIndex.put(null, sit);
		for(String s:states){
			if(s!=null)stateIndex.put(s, ++sit);
			writer.write(","+Sha.digest32HexSHA1(s));
		}
		writer.write("\r\n");
		ArrayList<Entry<Long,Float>> l = new ArrayList<Entry<Long,Float>>(transTotal.entrySet());    

        Collections.sort(l, new Comparator<Map.Entry<Long, Float>>() {
            public int compare(Map.Entry<Long, Float> o1, Map.Entry<Long, Float> o2) {
                return o2.getKey()==o1.getKey()?0:(o2.getKey()<o1.getKey()?1:-1);
            }
        });

		writer.write("%*****************details******************\r\n%");
		for(Entry<Long,Float> en:l){
			writer.write("************"+new Date(en.getKey())+"****"+Summary.intFormat.format(en.getKey())+","+transTotal.get(en.getKey())+"**************\r\n");
			for(String s:transStateTotal.get(en.getKey()).keySet()){
				writer.write(stateIndex.get(s)+"("+Summary.intFormat.format(transStateTotal.get(en.getKey()).get(s))+"):");
				for(String ts:transProperties.get(en.getKey()).get(s).keySet()){
					writer.write("|"+stateIndex.get(ts)+"-"+Summary.threeFloatFormat.format(transProperties.get(en.getKey()).get(s).get(ts)));
				}
				writer.write("|\r\n");
			}
		}
		return total;
	}

}
